package com.itjiange.reglg.service.impl;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itjiange.reglg.common.ErrorCode;
import com.itjiange.reglg.constant.CommonConstant;
import com.itjiange.reglg.constant.UserConstant;
import com.itjiange.reglg.exception.BussinessException;
import com.itjiange.reglg.mapper.UserInfoMapper;
import com.itjiange.reglg.model.pojo.UserInfo;
import com.itjiange.reglg.model.pojo.UserLog;
import com.itjiange.reglg.model.vo.UserLoginVO;
import com.itjiange.reglg.service.EmailService;
import com.itjiange.reglg.service.UserLogService;
import com.itjiange.reglg.service.UserService;
import com.itjiange.reglg.utils.NetUtils;
import com.itjiange.reglg.utils.RSAUtils;
import com.itjiange.reglg.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * <p>
 * 用户信息表 服务实现类
 * </p>
 *
 * @author ls
 * @since 2023-11-11
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserService {

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private UserLogService userLogService;

    @Autowired
    private EmailService emailService;

    /**
     * 根据用户名登录
     *
     * @param captchaUUID
     * @param publicKeyUUID
     * @param username
     * @param password
     * @param verCode
     * @param request
     * @param response
     * @return
     */
    @Override
    public UserLoginVO loginByUsername(String captchaUUID,
                                       String publicKeyUUID,
                                       String username,
                                       String password,
                                       String verCode,
                                       HttpServletRequest request,
                                       HttpServletResponse response) {
        // 判断是否校验验证码
        boolean ifVerCode = false;
        if (!CommonConstant.IS_EMPTY_STATE.equals(verCode)) {
            ifVerCode = true;
        }
        // 对验证码进行校验
        if (ifVerCode) {
            if (!redisUtils.ifHasKey(captchaUUID)) {
                redisUtils.removeByKey(captchaUUID); // 将验证码信息从 Redis 中清除
                throw new BussinessException(ErrorCode.PARAMS_ERROR, "验证码过期或不存在");
            }
            String captchaCode = redisUtils.getValueByKey(captchaUUID);
            if (!verCode.equals(captchaCode)) {
                redisUtils.removeByKey(captchaUUID); // 将验证码信息从 Redis 中清除
                throw new BussinessException(ErrorCode.PARAMS_ERROR, "验证码错误");
            }
        }
        if (!redisUtils.ifHasKey(publicKeyUUID)) {
            throw new BussinessException(ErrorCode.NOT_FOUND_ERROR);
        }
        // 获取私钥
        String privateKey = redisUtils.getValueByKey(publicKeyUUID);
        // 利用私钥解密，得到真正的密码
        String dePassword = decodePassword(password, privateKey);
        // 将 Redis 中的私钥删除
        redisUtils.removeByKey(publicKeyUUID);
        // 利用 md5 重新加密后查询数据库
        String mdPassword = SecureUtil.md5(dePassword + username);
        LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserInfo::getUsername, username);
        queryWrapper.eq(UserInfo::getPassword, mdPassword);
        // 获取 userInfo
        UserInfo userInfo = this.getOne(queryWrapper);
        if (userInfo == null) {
            throw new BussinessException(ErrorCode.SYSTEM_ERROR, "登录失败");
        }
        // 将登录状态加入 request 域中
        request.setAttribute(UserConstant.USER_LOGIN_STATE, UserConstant.USER_LOGIN_SUCCESS);
        // 如果是异地登录，则发送邮件提醒
        boolean flag = sendEmailWithDiffIP(request, userInfo);
        UserLoginVO userLoginVO = new UserLoginVO();
        userLoginVO.setUserId(userInfo.getUserId());
        userLoginVO.setIfReLogin(flag);
        // 登录成功时，生成 token 存入 Redis 中，并响应给前端
        userLoginVO.setToken(setRedisWithToken());
        return userLoginVO;
    }

    /**
     * 生成 token 存入 Redis 中
     * @return 存入 Redis 中的 key
     */
    private String setRedisWithToken() {
        String token = UUID.randomUUID().toString(); // 将生成的 uuid 作为 token
        String redisKeyByToken = UserConstant.USER_LOGIN_TOKEN.concat(token);
        // 存入 Redis 中
        redisUtils.setKV(redisKeyByToken, UserConstant.USER_LOGIN_STATE, 60 * 60 * 24 * 7);
        return redisKeyByToken;
    }

    /**
     * 发送邮件提醒
     * @param request
     * @param userInfo
     * @return
     */
    private boolean sendEmailWithDiffIP(HttpServletRequest request, UserInfo userInfo) {
        boolean flag = ifUserInfoSameIP(request, userInfo);
        if (flag) {
            String localhost = NetUtils.getIpLocalhost(request);
            emailService.sendIPMsgEmail(localhost, userInfo.getEmail());
        }
        return flag;
    }

    /**
     * 根据邮箱或电话登录
     *
     * @param emailOrPhone
     * @param verCode
     * @param request
     * @return
     */
    @Override
    public UserLoginVO loginByEmailOrPhone(String emailOrPhone, String verCode, HttpServletRequest request) {
        String validate = validateByLog(emailOrPhone, verCode);
        LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
        if (CommonConstant.EMAIL.equals(validate)) {
            queryWrapper.eq(UserInfo::getEmail, emailOrPhone);
        } else if (CommonConstant.PHONE.equals(validate)) {
            queryWrapper.eq(UserInfo::getPhone,  emailOrPhone);
        }
        // 获取 userInfo
        UserInfo userInfo = this.getOne(queryWrapper);
        if (userInfo == null) {
            throw new BussinessException(ErrorCode.SYSTEM_ERROR);
        }
        boolean flag = ifUserInfoSameIP(request, userInfo);
        UserLoginVO userLoginVO = new UserLoginVO();
        userLoginVO.setUserId(userInfo.getUserId());
        userLoginVO.setIfReLogin(flag);
        return userLoginVO;
    }

    /**
     * 判断是否异地登录
     * @param request
     * @param userInfo
     * @return
     */
    private boolean ifUserInfoSameIP(HttpServletRequest request, UserInfo userInfo) {
        // 获取 ip 归属地
        String localhost = NetUtils.getIpLocalhost(request);
        LambdaQueryWrapper<UserLog> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserLog::getUserId, userInfo.getUserId());
        // 获取 log 表中的 ip 归属地
        UserLog userLogg = userLogService.getOne(queryWrapper);
        UserLog userLog = new UserLog();
        userLog.setUserId(userInfo.getUserId());
        userLog.setIpLocation(localhost);
        if (userLogg == null) {
            boolean userLogFlag = userLogService.save(userLog);
            if (!userLogFlag) {
                throw new BussinessException(ErrorCode.SYSTEM_ERROR);
            }
        }
        if (userLogg != null && !localhost.equals(userLogg.getIpLocation())) {
            userLogService.update(userLog, queryWrapper);
            return true;
        }
        return false;
    }

    /**
     * 利用用户名注册
     *
     * @param uuid
     * @param username
     * @param password
     * @param checkPassword
     * @param verCode
     * @return
     */
    @Override
    public boolean registerByUsername(String uuid, String username, String password, String checkPassword, String verCode) {
        // 从 redis 中获取图形验证码和私钥
        String capKey = uuid + CommonConstant.CAPTCHA;
        if (!redisUtils.ifHasKey(capKey)) {
            throw new BussinessException(ErrorCode.PARAMS_ERROR, "验证码过期或不存在");
        }
        // 获取验证码，私钥
        String captchaCode = redisUtils.getValueByKey(capKey);
        String privateKey = redisUtils.getValueByKey(uuid);
        if (!captchaCode.equals(verCode)) {
            throw new BussinessException(ErrorCode.PARAMS_ERROR, "验证码错误");
        }
        if (!password.equals(checkPassword)) {
            throw new BussinessException(ErrorCode.PARAMS_ERROR, "两次密码不一致");
        }
        if (getUserByUsername(username)) {
            throw new BussinessException(ErrorCode.PARAMS_ERROR, "用户名已存在，请登录");
        }
        // 利用私钥解密，得到真正的密码
        String dePassword = decodePassword(password, privateKey);
        // 利用 md5 重新加密后存入数据库
        String mdPassword = SecureUtil.md5(dePassword + username);
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername(username);
        userInfo.setPassword(mdPassword);
        return this.save(userInfo);
    }

    /**
     * 用户邮箱或电话注册
     *
     * @param emailOrPhone
     * @param verCode
     * @return
     */
    @Override
    public boolean registerByEmailOrPhone(String emailOrPhone, String verCode) {
        // 校验
        String validate = validateByReg(emailOrPhone, verCode);
        UserInfo userInfo = new UserInfo();
        if (CommonConstant.EMAIL.equals(validate)) {
            userInfo.setEmail(emailOrPhone);
        } else if (CommonConstant.PHONE.equals(validate)) {
            userInfo.setPhone(emailOrPhone);
        }
        return this.save(userInfo);
    }

    /**
     * 利用私钥解密
     * @param passCode
     * @param privateKey
     * @return
     */
    private String decodePassword(String passCode, String privateKey) {
        return RSAUtils.decryptPass(passCode, privateKey);
    }

    /**
     * 注册校验
     * @param emailOrPhone
     * @param verCode
     * @return
     */
    private String validateByReg(String emailOrPhone, String verCode) {
        String flag = "";
        // 验证是邮箱
        if (Validator.isEmail(emailOrPhone)) {
            flag = validateByEmailOrPhoneByReg(emailOrPhone, verCode, CommonConstant.EMAIL);
        // 验证是移动电话
        } else if (Validator.isMobile(emailOrPhone)) {
            flag = validateByEmailOrPhoneByReg(emailOrPhone, verCode, CommonConstant.PHONE);
        }
        if (StrUtil.isEmptyIfStr(flag)) {
            throw new BussinessException(ErrorCode.PARAMS_ERROR, "输入格式有误");
        }
        return flag;
    }

    private String validateByEmailOrPhoneByReg(String emailOrPhone, String verCode, String flag) {
        // 判断 redis 中是否存在验证码
        if (!redisUtils.ifHasKey(emailOrPhone)) {
            throw new BussinessException(ErrorCode.NOT_FOUND_ERROR, "Redis 中验证码不存在");
        }
        // 唯一性校验
        if (CommonConstant.EMAIL.equals(flag)) {
            getUserByEmail(emailOrPhone);
        } else if (CommonConstant.PHONE.equals(flag)) {
            getUserByPhone(emailOrPhone);
        }
        // 校验接收到的验证码
        String getCode = redisUtils.getValueByKey(emailOrPhone);
        if (!verCode.equals(getCode)) {
            throw new BussinessException(ErrorCode.PARAMS_ERROR, "验证码不匹配，请重新输入验证码");
        }
        return flag;
    }

    /**
     * 登录校验
     * @param emailOrPhone
     * @param verCode
     * @return
     */
    private String validateByLog(String emailOrPhone, String verCode) {
        String flag = "";
        // 验证是邮箱
        if (Validator.isEmail(emailOrPhone)) {
            flag = validateByEmailOrPhoneByLog(emailOrPhone, verCode, CommonConstant.EMAIL);
        // 验证是移动电话
        } else if (Validator.isMobile(emailOrPhone)) {
            flag = validateByEmailOrPhoneByLog(emailOrPhone, verCode, CommonConstant.PHONE);
        }
        if (StrUtil.isEmptyIfStr(flag)) {
            throw new BussinessException(ErrorCode.PARAMS_ERROR, "输入格式有误");
        }
        return flag;
    }

    private String validateByEmailOrPhoneByLog(String emailOrPhone, String verCode, String flag) {
        // 判断 redis 中是否存在验证码
        if (!redisUtils.ifHasKey(emailOrPhone)) {
            throw new BussinessException(ErrorCode.NOT_FOUND_ERROR, "Redis 中验证码不存在");
        }
        // 校验接收到的验证码
        String getCode = redisUtils.getValueByKey(emailOrPhone);
        if (!verCode.equals(getCode)) {
            throw new BussinessException(ErrorCode.PARAMS_ERROR, "验证码不匹配，请重新输入验证码");
        }
        return flag;
    }

    /**
     * 检测邮箱是否重复
     * @param email 用户名
     * @return true 表示重复，false 表示不重复
     */
    private void getUserByEmail(String email) {
        LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserInfo::getEmail, email);
        UserInfo userInfo = this.getOne(queryWrapper);
        if (userInfo != null) {
            throw new BussinessException(ErrorCode.PARAMS_ERROR, "邮箱已注册，请登录");
        }
    }

    /**
     * 检测用户名是否重复
     * @param username 用户名
     * @return true 表示重复，false 表示不重复
     */
    private boolean getUserByUsername(String username) {
        LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserInfo::getUsername, username);
        UserInfo userInfo = this.getOne(queryWrapper);
        return userInfo != null;
    }

    /**
     * 检测电话是否重复
     * @param phone
     * @return
     */
    private void getUserByPhone(String phone) {
        LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserInfo::getPhone, phone);
        UserInfo userInfo = this.getOne(queryWrapper);
        if (userInfo != null) {
            throw new BussinessException(ErrorCode.PARAMS_ERROR, "电话已注册，请登录");
        }
    }
}
